/*
 * 将Python异常报告为PostgreSQL错误
 *
 * src/pl/plpython/plpy_elog.c
 */

#include "postgres.h"

#include "lib/stringinfo.h"
#include "plpy_elog.h"
#include "plpy_main.h"
#include "plpy_procedure.h"
#include "plpython.h"

PyObject   *PLy_exc_error = NULL;
PyObject   *PLy_exc_fatal = NULL;
PyObject   *PLy_exc_spi_error = NULL;


static void fc_PLy_traceback(PyObject *fc_e, PyObject *fc_v, PyObject *fc_tb,
						  char **fc_xmsg, char **fc_tbmsg, int *fc_tb_depth);
static void fc_PLy_get_spi_error_data(PyObject *fc_exc, int *fc_sqlerrcode, char **fc_detail,
								   char **fc_hint, char **fc_query, int *fc_position,
								   char **fc_schema_name, char **fc_table_name, char **fc_column_name,
								   char **fc_datatype_name, char **fc_constraint_name);
static void fc_PLy_get_error_data(PyObject *fc_exc, int *fc_sqlerrcode, char **fc_detail,
							   char **fc_hint, char **fc_schema_name, char **fc_table_name, char **fc_column_name,
							   char **fc_datatype_name, char **fc_constraint_name);
static char *fc_get_source_line(const char *fc_src, int fc_lineno);

static void fc_get_string_attr(PyObject *fc_obj, char *fc_attrname, char **fc_str);
static bool fc_set_string_attr(PyObject *fc_obj, char *fc_attrname, char *fc_str);

/*
 * 发出PG错误或通知，以及有关当前Python错误的任何可用信息，
 * 这些信息之前由PLy_exception_set()设置。
 * 这应该用于将Python错误传播到PG。如果fmt为
 * NULL，Python错误将成为主要错误消息，否则
 * 它将成为详细信息。如果有Python traceback，它将被放入
 * 上下文中。
 */
void PLy_elog_impl(int fc_elevel, const char *fc_fmt,...)
{
	int			fc_save_errno = errno;
	char	   *fc_xmsg;
	char	   *fc_tbmsg;
	int			fc_tb_depth;
	StringInfoData fc_emsg;
	PyObject   *fc_exc,
			   *fc_val,
			   *fc_tb;
	const char *fc_primary = NULL;
	int			fc_sqlerrcode = 0;
	char	   *fc_detail = NULL;
	char	   *fc_hint = NULL;
	char	   *fc_query = NULL;
	int			fc_position = 0;
	char	   *fc_schema_name = NULL;
	char	   *fc_table_name = NULL;
	char	   *fc_column_name = NULL;
	char	   *fc_datatype_name = NULL;
	char	   *fc_constraint_name = NULL;

	PyErr_Fetch(&fc_exc, &fc_val, &fc_tb);

	if (fc_exc != NULL)
	{
		PyErr_NormalizeException(&fc_exc, &fc_val, &fc_tb);

		if (PyErr_GivenExceptionMatches(fc_val, PLy_exc_spi_error))
			fc_PLy_get_spi_error_data(fc_val, &fc_sqlerrcode,
								   &fc_detail, &fc_hint, &fc_query, &fc_position,
								   &fc_schema_name, &fc_table_name, &fc_column_name,
								   &fc_datatype_name, &fc_constraint_name);
		else if (PyErr_GivenExceptionMatches(fc_val, PLy_exc_error))
			fc_PLy_get_error_data(fc_val, &fc_sqlerrcode, &fc_detail, &fc_hint,
							   &fc_schema_name, &fc_table_name, &fc_column_name,
							   &fc_datatype_name, &fc_constraint_name);
		else if (PyErr_GivenExceptionMatches(fc_val, PLy_exc_fatal))
			fc_elevel = FATAL;
	}

	/* 这释放了我们对tb的引用计数！ */
	fc_PLy_traceback(fc_exc, fc_val, fc_tb,
				  &fc_xmsg, &fc_tbmsg, &fc_tb_depth);

	if (fc_fmt)
	{
		initStringInfo(&fc_emsg);
		for (;;)
		{
			va_list		fc_ap;
			int			fc_needed;

			errno = fc_save_errno;
			va_start(fc_ap, fc_fmt);
			fc_needed = appendStringInfoVA(&fc_emsg, dgettext(TEXTDOMAIN, fc_fmt), fc_ap);
			va_end(fc_ap);
			if (fc_needed == 0)
				break;
			enlargeStringInfo(&fc_emsg, fc_needed);
		}
		fc_primary = fc_emsg.data;

		/* 如果有异常消息，它将放入详细信息中。 */
		if (fc_xmsg)
			fc_detail = fc_xmsg;
	}
	else
	{
		if (fc_xmsg)
			fc_primary = fc_xmsg;
	}

	PG_TRY();
	{
		ereport(fc_elevel,
				(errcode(fc_sqlerrcode ? fc_sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
				 errmsg_internal("%s", fc_primary ? fc_primary : "no exception data"),
				 (fc_detail) ? errdetail_internal("%s", fc_detail) : 0,
				 (fc_tb_depth > 0 && fc_tbmsg) ? errcontext("%s", fc_tbmsg) : 0,
				 (fc_hint) ? errhint("%s", fc_hint) : 0,
				 (fc_query) ? internalerrquery(fc_query) : 0,
				 (fc_position) ? internalerrposition(fc_position) : 0,
				 (fc_schema_name) ? err_generic_string(PG_DIAG_SCHEMA_NAME,
													fc_schema_name) : 0,
				 (fc_table_name) ? err_generic_string(PG_DIAG_TABLE_NAME,
												   fc_table_name) : 0,
				 (fc_column_name) ? err_generic_string(PG_DIAG_COLUMN_NAME,
													fc_column_name) : 0,
				 (fc_datatype_name) ? err_generic_string(PG_DIAG_DATATYPE_NAME,
													  fc_datatype_name) : 0,
				 (fc_constraint_name) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME,
														fc_constraint_name) : 0));
	}
	PG_FINALLY();
	{
		if (fc_fmt)
			pfree(fc_emsg.data);
		if (fc_xmsg)
			pfree(fc_xmsg);
		if (fc_tbmsg)
			pfree(fc_tbmsg);
		Py_XDECREF(fc_exc);
		Py_XDECREF(fc_val);
	}
	PG_END_TRY();
}

/*
 * 从给定异常数据中提取Python traceback。
 *
 * 异常错误消息返回在xmsg中，traceback在
 * tbmsg中（两者均为palloc'd字符串），traceback深度在
 * tb_depth中。
 *
 * 我们释放traceback堆栈中所有Python对象的引用计数，
 * 但不包括e或v。
 */
static void fc_PLy_traceback(PyObject *fc_e, PyObject *fc_v, PyObject *fc_tb,
			  char **fc_xmsg, char **fc_tbmsg, int *fc_tb_depth)
{
	PyObject   *fc_e_type_o;
	PyObject   *fc_e_module_o;
	char	   *fc_e_type_s = NULL;
	char	   *fc_e_module_s = NULL;
	PyObject   *fc_vob = NULL;
	char	   *fc_vstr;
	StringInfoData fc_xstr;
	StringInfoData fc_tbstr;

	/*
	 * 如果没有异常，返回空值
	 */
	if (fc_e == NULL)
	{
		*fc_xmsg = NULL;
		*fc_tbmsg = NULL;
		*fc_tb_depth = 0;

		return;
	}

	/*
	 * 格式化异常及其值并将其放入xmsg中。
	 */

	fc_e_type_o = PyObject_GetAttrString(fc_e, "__name__");
	fc_e_module_o = PyObject_GetAttrString(fc_e, "__module__");
	if (fc_e_type_o)
		fc_e_type_s = PLyUnicode_AsString(fc_e_type_o);
	if (fc_e_type_s)
		fc_e_module_s = PLyUnicode_AsString(fc_e_module_o);

	if (fc_v && ((fc_vob = PyObject_Str(fc_v)) != NULL))
		fc_vstr = PLyUnicode_AsString(fc_vob);
	else
		fc_vstr = "unknown";

	initStringInfo(&fc_xstr);
	if (!fc_e_type_s || !fc_e_module_s)
	{
		/* 不应该发生 */
		appendStringInfoString(&fc_xstr, "unrecognized exception");
	}
	/* 模拟traceback.format_exception_only的行为 */
	else if (strcmp(fc_e_module_s, "builtins") == 0
			 || strcmp(fc_e_module_s, "__main__") == 0
			 || strcmp(fc_e_module_s, "exceptions") == 0)
		appendStringInfoString(&fc_xstr, fc_e_type_s);
	else
		appendStringInfo(&fc_xstr, "%s.%s", fc_e_module_s, fc_e_type_s);
	appendStringInfo(&fc_xstr, ": %s", fc_vstr);

	*fc_xmsg = fc_xstr.data;

	/*
	 * 现在格式化traceback并将其放入tbmsg中。
	 */

	*fc_tb_depth = 0;
	initStringInfo(&fc_tbstr);
	/* 尽可能模拟Python traceback报告。 */
	appendStringInfoString(&fc_tbstr, "Traceback (most recent call last):");
	while (fc_tb != NULL && fc_tb != Py_None)
	{
		PyObject   *volatile fc_tb_prev = NULL;
		PyObject   *volatile fc_frame = NULL;
		PyObject   *volatile fc_code = NULL;
		PyObject   *volatile fc_name = NULL;
		PyObject   *volatile fc_lineno = NULL;
		PyObject   *volatile fc_filename = NULL;

		PG_TRY();
		{
			fc_lineno = PyObject_GetAttrString(fc_tb, "tb_lineno");
			if (fc_lineno == NULL)
				elog(ERROR, "could not get line number from Python traceback");

			fc_frame = PyObject_GetAttrString(fc_tb, "tb_frame");
			if (fc_frame == NULL)
				elog(ERROR, "could not get frame from Python traceback");

			fc_code = PyObject_GetAttrString(fc_frame, "f_code");
			if (fc_code == NULL)
				elog(ERROR, "could not get code object from Python frame");

			fc_name = PyObject_GetAttrString(fc_code, "co_name");
			if (fc_name == NULL)
				elog(ERROR, "could not get function name from Python code object");

			fc_filename = PyObject_GetAttrString(fc_code, "co_filename");
			if (fc_filename == NULL)
				elog(ERROR, "could not get file name from Python code object");
		}
		PG_CATCH();
		{
			Py_XDECREF(fc_frame);
			Py_XDECREF(fc_code);
			Py_XDECREF(fc_name);
			Py_XDECREF(fc_lineno);
			Py_XDECREF(fc_filename);
			PG_RE_THROW();
		}
		PG_END_TRY();

		/* 第一个帧始终指向<module>，跳过它。 */
		if (*fc_tb_depth > 0)
		{
			PLyExecutionContext *fc_exec_ctx = PLy_current_execution_context();
			char	   *fc_proname;
			char	   *fc_fname;
			char	   *fc_line;
			char	   *fc_plain_filename;
			long		fc_plain_lineno;

			/*
			 * 第二个帧指向内部函数，但为了模拟
			 * Python错误报告，我们想说<module>。
			 */
			if (*fc_tb_depth == 1)
				fc_fname = "<module>";
			else
				fc_fname = PLyUnicode_AsString(fc_name);

			fc_proname = PLy_procedure_name(fc_exec_ctx->curr_proc);
			fc_plain_filename = PLyUnicode_AsString(fc_filename);
			fc_plain_lineno = PyLong_AsLong(fc_lineno);

			if (fc_proname == NULL)
				appendStringInfo(&fc_tbstr, "\n  PL/Python anonymous code block, line %ld, in %s",
								 fc_plain_lineno - 1, fc_fname);
			else
				appendStringInfo(&fc_tbstr, "\n  PL/Python function \"%s\", line %ld, in %s",
								 fc_proname, fc_plain_lineno - 1, fc_fname);

			/*
			 * 函数代码对象是使用"<string>"作为
			 * 文件名编译的
			 */
			if (fc_exec_ctx->curr_proc && fc_plain_filename != NULL &&
				strcmp(fc_plain_filename, "<string>") == 0)
			{
				/*
				 * 如果我们知道当前过程，将确切行附加
				 * 来自源代码，再次模拟Python的traceback.py
				 * 模块行为。我们可以存储已经分行的
				 * 源代码，以避免每次都进行分割，但产生一个
				 * traceback并不是最重要的优化场景。
				 * 但我们不会像traceback.py那样读取
				 * 导入模块的源代码。
				 */
				fc_line = fc_get_source_line(fc_exec_ctx->curr_proc->src, fc_plain_lineno);
				if (fc_line)
				{
					appendStringInfo(&fc_tbstr, "\n    %s", fc_line);
					pfree(fc_line);
				}
			}
		}

		Py_DECREF(fc_frame);
		Py_DECREF(fc_code);
		Py_DECREF(fc_name);
		Py_DECREF(fc_lineno);
		Py_DECREF(fc_filename);

		/* 释放当前帧并转到下一帧。 */
		fc_tb_prev = fc_tb;
		fc_tb = PyObject_GetAttrString(fc_tb, "tb_next");
		Assert(fc_tb_prev != Py_None);
		Py_DECREF(fc_tb_prev);
		if (fc_tb == NULL)
			elog(ERROR, "could not traverse Python traceback");
		(*fc_tb_depth)++;
	}

	/* 返回traceback。 */
	*fc_tbmsg = fc_tbstr.data;

	Py_XDECREF(fc_e_type_o);
	Py_XDECREF(fc_e_module_o);
	Py_XDECREF(fc_vob);
}

/*
 * 从SPIError的sqlstate属性中提取错误代码。
 */
static void fc_PLy_get_sqlerrcode(PyObject *fc_exc, int *fc_sqlerrcode)
{
	PyObject   *fc_sqlstate;
	char	   *fc_buffer;

	fc_sqlstate = PyObject_GetAttrString(fc_exc, "sqlstate");
	if (fc_sqlstate == NULL)
		return;

	fc_buffer = PLyUnicode_AsString(fc_sqlstate);
	if (strlen(fc_buffer) == 5 &&
		strspn(fc_buffer, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
	{
		*fc_sqlerrcode = MAKE_SQLSTATE(fc_buffer[0], fc_buffer[1], fc_buffer[2],
									fc_buffer[3], fc_buffer[4]);
	}

	Py_DECREF(fc_sqlstate);
}

/*
 * 从SPIError中提取错误数据
 */
static void fc_PLy_get_spi_error_data(PyObject *fc_exc, int *fc_sqlerrcode, char **fc_detail,
					   char **fc_hint, char **fc_query, int *fc_position,
					   char **fc_schema_name, char **fc_table_name,
					   char **fc_column_name,
					   char **fc_datatype_name, char **fc_constraint_name)
{
	PyObject   *fc_spidata;

	fc_spidata = PyObject_GetAttrString(fc_exc, "spidata");

	if (fc_spidata != NULL)
	{
		PyArg_ParseTuple(fc_spidata, "izzzizzzzz",
						fc_sqlerrcode, fc_detail, fc_hint, fc_query, fc_position,
						fc_schema_name, fc_table_name, fc_column_name,
						fc_datatype_name, fc_constraint_name);
	}
	else
	{
		/*
		 * 如果没有spidata，至少设置sqlerrcode。这可能发生
		 * 如果有人显式从Python代码中引发SPI异常。
		 */
		fc_PLy_get_sqlerrcode(fc_exc, fc_sqlerrcode);
	}

	Py_XDECREF(fc_spidata);
}

/*
 * 从Error中提取错误数据。
 *
 * 注意：位置和查询属性永远不会为Error设置，因此，与
 * PLy_get_spi_error_data不同，此函数不会返回它们。
 */
static void fc_PLy_get_error_data(PyObject *fc_exc, int *fc_sqlerrcode, char **fc_detail, char **fc_hint,
				   char **fc_schema_name, char **fc_table_name, char **fc_column_name,
				   char **fc_datatype_name, char **fc_constraint_name)
{
	fc_PLy_get_sqlerrcode(fc_exc, fc_sqlerrcode);
	fc_get_string_attr(fc_exc, "detail", fc_detail);
	fc_get_string_attr(fc_exc, "hint", fc_hint);
	fc_get_string_attr(fc_exc, "schema_name", fc_schema_name);
	fc_get_string_attr(fc_exc, "table_name", fc_table_name);
	fc_get_string_attr(fc_exc, "column_name", fc_column_name);
	fc_get_string_attr(fc_exc, "datatype_name", fc_datatype_name);
	fc_get_string_attr(fc_exc, "constraint_name", fc_constraint_name);
}

/*
 * 将给定的源行作为palloc'd字符串获取
 */
static char * fc_get_source_line(const char *fc_src, int fc_lineno)
{
	const char *fc_s = NULL;
	const char *fc_next = fc_src;
	int			fc_current = 0;

	/* 计算前一个数据项的长度 */
	if (fc_lineno <= 0)
		return NULL;

	while (fc_current < fc_lineno)
	{
		fc_s = fc_next;
		fc_next = strchr(fc_s + 1, '\n');
		fc_current++;
		if (fc_next == NULL)
			break;
	}

	if (fc_current != fc_lineno)
		return NULL;

	while (*fc_s && isspace((unsigned char) *fc_s))
		fc_s++;

	if (fc_next == NULL)
		return pstrdup(fc_s);

	/*
	 * 理智检查，如果该行全是空白，则下一个<s。
	 * 这在Python报告在该行创建的帧时不应该发生，但还是要检查。
	 */
	if (fc_next < fc_s)
		return NULL;

	return pnstrdup(fc_s, fc_next - fc_s);
}


/* 使用vprint接口和翻译支持调用PyErr_SetString */
void PLy_exception_set(PyObject *fc_exc, const char *fc_fmt,...)
{
	char		fc_buf[1024];
	va_list		fc_ap;

	va_start(fc_ap, fc_fmt);
	vsnprintf(fc_buf, sizeof(fc_buf), dgettext(TEXTDOMAIN, fc_fmt), fc_ap);
	va_end(fc_ap);

	PyErr_SetString(fc_exc, fc_buf);
}

/* 同样，与复数化消息 */
void PLy_exception_set_plural(PyObject *fc_exc,
						 const char *fc_fmt_singular, const char *fc_fmt_plural,
						 unsigned long fc_n,...)
{
	char		fc_buf[1024];
	va_list		fc_ap;

	va_start(fc_ap, fc_n);
	vsnprintf(fc_buf, sizeof(fc_buf),
			  dngettext(TEXTDOMAIN, fc_fmt_singular, fc_fmt_plural, fc_n),
			  fc_ap);
	va_end(fc_ap);

	PyErr_SetString(fc_exc, fc_buf);
}

/* 将给定异常的属性设置为来自ErrorData的详细信息 */
void PLy_exception_set_with_details(PyObject *fc_excclass, ErrorData *fc_edata)
{
	PyObject   *fc_args = NULL;
	PyObject   *fc_error = NULL;

	fc_args = Py_BuildValue("(s)", fc_edata->message);
	if (!fc_args)
		goto failure;

	/* 创建一个新的异常，以错误消息作为参数 */  
	fc_error = PyObject_CallObject(fc_excclass, fc_args);
	if (!fc_error)
		goto failure;

	if (!fc_set_string_attr(fc_error, "sqlstate",
						 unpack_sql_state(fc_edata->sqlerrcode)))
		goto failure;

	if (!fc_set_string_attr(fc_error, "detail", fc_edata->detail))
		goto failure;

	if (!fc_set_string_attr(fc_error, "hint", fc_edata->hint))
		goto failure;

	if (!fc_set_string_attr(fc_error, "query", fc_edata->internalquery))
		goto failure;

	if (!fc_set_string_attr(fc_error, "schema_name", fc_edata->schema_name))
		goto failure;

	if (!fc_set_string_attr(fc_error, "table_name", fc_edata->table_name))
		goto failure;

	if (!fc_set_string_attr(fc_error, "column_name", fc_edata->column_name))
		goto failure;

	if (!fc_set_string_attr(fc_error, "datatype_name", fc_edata->datatype_name))
		goto failure;

	if (!fc_set_string_attr(fc_error, "constraint_name", fc_edata->constraint_name))
		goto failure;

	PyErr_SetObject(fc_excclass, fc_error);

	Py_DECREF(fc_args);
	Py_DECREF(fc_error);

	return;

failure:
	Py_XDECREF(fc_args);
	Py_XDECREF(fc_error);

	elog(ERROR, "could not convert error to Python exception");
}

/* 获取对象属性的字符串值 */  
static void fc_get_string_attr(PyObject *fc_obj, char *fc_attrname, char **fc_str)
{
	PyObject   *fc_val;

	fc_val = PyObject_GetAttrString(fc_obj, fc_attrname);
	if (fc_val != NULL && fc_val != Py_None)
	{
		*fc_str = pstrdup(PLyUnicode_AsString(fc_val));
	}
	Py_XDECREF(fc_val);
}

/* 将对象属性设置为字符串值，当设置成功时返回 true */  
static bool fc_set_string_attr(PyObject *fc_obj, char *fc_attrname, char *fc_str)
{
	int			fc_result;
	PyObject   *fc_val;

	if (fc_str != NULL)
	{
		fc_val = PLyUnicode_FromString(fc_str);
		if (!fc_val)
			return false;
	}
	else
	{
		fc_val = Py_None;
		Py_INCREF(Py_None);
	}

	fc_result = PyObject_SetAttrString(fc_obj, fc_attrname, fc_val);
	Py_DECREF(fc_val);

	return fc_result != -1;
}
